package com.gitee.apanlh.util.setting;

import com.gitee.apanlh.exp.NotFoundException;
import com.gitee.apanlh.exp.RuntimeIoException;
import com.gitee.apanlh.exp.StreamReadException;
import com.gitee.apanlh.util.base.CollUtils;
import com.gitee.apanlh.util.base.Eq;
import com.gitee.apanlh.util.base.StringUtils;
import com.gitee.apanlh.util.encode.CharsetCode;
import com.gitee.apanlh.util.file.FileMatchType;
import com.gitee.apanlh.util.io.IOUtils;
import com.gitee.apanlh.util.reflection.ClassUtils;
import com.gitee.apanlh.util.valid.Assert;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.jar.JarFile;

/**
 * 	资源加载和路径获取等相关工具类
 * 	<p>如果需要操作相关资源配置文件等请查看{@link PropertiesUtils}类
 * 	<p>{@link ConfigLoader}
 * 	
 * 	@author Pan
 */
public class ResourceUtils {
	
	private static final String PATH_ERROR = "path [{}] does not exist";
	
	/**
	 * 	构造函数
	 * 
	 * 	@author Pan
	 */
	private ResourceUtils() {
		//	不允许外部实例
		super();
	}
	
	/**	
	 * 	资源地址不限于:"classpath:", "file:", "jar:", "war:"
	 * <br>输入流需自行关闭
	 * 
	 * 	@author Pan
	 * 	@param 	path	资源地址
	 * 	@return	InputStream
	 * 	@throws StreamReadException	如果获取Stream异常时抛出
	 */
	public static InputStream getInput(String path) {
		return getInput(getUrl(path));
	}
	
	/**	
	 * 	资源地址不限于:"classpath:", "file:", "jar:", "war:"
	 * <br>输入流需自行关闭
	 * 
	 * 	@author Pan
	 * 	@param 	url		URL地址 
	 * 	@return	InputStream
	 * 	@throws StreamReadException	如果获取Stream异常时抛出
	 */
	public static InputStream getInput(URL url) {
		Assert.isNotNull(url);
		
		try {
			return url.openStream();
		} catch (Exception e) {
			throw new StreamReadException(e.getMessage(), e);
		}
	}
	
	/**	
	 * 	资源地址不限于:"classpath:", "file:", "jar:", "war:"
	 * 	<br>默认UTF-8字符集编码
	 * 	<br>输入流需自行关闭
	 * 	
	 * 	@author Pan
	 * 	@param 	path	资源地址
	 * 	@return	BufferedReader
	 * 	@throws NotFoundException	如果未找到对应资源则抛出
	 */
	public static BufferedReader getReader(String path) {
		return getReader(path, CharsetCode.UTF_8);
	}
	
	/**	
	 * 	资源地址不限于:"classpath:", "file:", "jar:", "war:"
	 * 	<br>自定义字符集编码
	 * 	<br>输入流需自行关闭
	 * 	
	 * 	@author Pan
	 * 	@param 	path	资源地址
	 * 	@param 	charset	字符集编码
	 * 	@return	BufferedReader
	 * 	@throws NotFoundException	如果未找到对应资源则抛出
	 */
	public static BufferedReader getReader(String path, String charset) {
		return IOUtils.getReader(getInput(path), CharsetCode.getCharset(charset, CharsetCode.CHARSET_UTF_8));
	}
	
	/**	
	 * 	资源地址不限于:"classpath:", "file:", "jar:", "war:"
	 * 	<br>默认UTF-8字符集编码
	 * 	<br>输入流需自行关闭
	 * 	
	 * 	@author Pan
	 * 	@param 	url		URL对象
	 * 	@return	BufferedReader
	 * 	@throws NotFoundException	如果未找到对应资源则抛出
	 */
	public static BufferedReader getReader(URL url) {
		return getReader(url, CharsetCode.UTF_8);
	}
	
	/**	
	 * 	资源地址不限于:"classpath:", "file:", "jar:", "war:"
	 * 	<br>自定义字符集编码
	 * 	<br>输入流需自行关闭
	 * 	
	 * 	@author Pan
	 * 	@param 	url		URL对象
	 * 	@param 	charset	字符集编码
	 * 	@return	BufferedReader
	 * 	@throws NotFoundException	如果未找到对应资源则抛出
	 */
	public static BufferedReader getReader(URL url, String charset) {
		return IOUtils.getReader(getInput(url), CharsetCode.getCharset(charset, CharsetCode.CHARSET_UTF_8));
	}
	
	/**
	 * 	资源地址不限于:"classpath:", "classes/", "file:", "jar:", "war:"
	 * 	<br>classes/可以不填写，如果直接a.properties时则自动获取classes下
	 * 	
	 * 	@author Pan
	 * 	@param 	path	资源地址
	 * 	@return	URL
	 * 	@throws NotFoundException	如果未找到对应资源则抛出
	 */
	public static URL getUrl(String path) {
		return getUrl(null, path);
	}
	
	/**
	 * 	基于类位置进行资源加载
	 * 	<br>例如 {@code A.class(假设A.class存在com/pan/util/下), abc.properties. -> 将寻找 com/pan/util/abc.properties}
	 *  	
	 * 	@author Pan
	 * 	@param 	clazz	类
	 * 	@param 	path	资源地址
	 * 	@return	URL
	 * 	@throws NotFoundException	如果未找到对应资源则抛出
	 */
	public static URL getUrl(Class<?> clazz, String path) {
		Assert.isNotEmpty(path);
		
		if (clazz == null) {
			//	classpath
			if (ResourceType.isPrefix(path, ResourceType.PREFIX_CLASSPATH)) {
				return getResource(null, StringUtils.subStr(path, ResourceType.PREFIX_CLASSPATH.getValue().length()));
			}
			//	classes
			if (ResourceType.isPrefix(path, ResourceType.PREFIX_CLASSES)) {
				return getResource(null, StringUtils.subStr(path, ResourceType.PREFIX_CLASSES.getValue().length()));
			}
		}
		
		try {
			return new URL(path);
		} catch (MalformedURLException e) {
			try {
				//  基于类位置进行资源加载
				return getResource(clazz, path);
			} catch (Exception e2) {
				//	如果出现未能解析URL时，尝试File解析
				try {
					File file = new File(path);
					if (!file.exists()) {
						throw new NotFoundException(StringUtils.format(PATH_ERROR, file.toURI().getRawPath().substring(1)));
					}
					return file.toURI().toURL();
				}
				catch (Exception e3) {
					throw new NotFoundException(e3.getMessage(), e);
				}
			}
		}
	}
	
	/**	
	 * 	根据资源路径获取资源
	 * 	<br>根据线程类加载器或JVM类加载器
	 * 	
	 * 	@author Pan
	 * 	@param 	path	地址
	 * 	@return	URL
	 * 	@throws NotFoundException 如果地址未能解析成URL则抛出
	 */
	public static URL getResource(String path) {
		URL resource = ClassUtils.getClassLoader().getResource(path);
		if (resource == null) {
			throw new NotFoundException(StringUtils.format(PATH_ERROR, path));
		}
		return resource;
	}
	
	/**	
	 * 	根据指定类的资源路径获取资源
	 * 	
	 * 	@author Pan
	 * 	@param 	clazz	类
	 * 	@param 	path	地址
	 * 	@return	URL
	 * 	@throws NotFoundException 如果地址未能解析成URL则抛出
	 */
	public static URL getResource(Class<?> clazz, String path) {
		if (clazz == null) {
			return getResource(path);
		}
		
		URL resource = clazz.getResource(path);
		if (resource == null) {
			throw new NotFoundException(StringUtils.format(PATH_ERROR, path));
		}
		return resource;
	}
	
	/**	
	 * 	根据资源路径获取多个资源
	 * 	<br>必须为目录路径格式如:config/x
	 * 	<br>根据线程类加载器或JVM类加载器
	 * 	
	 * 	@author Pan
	 * 	@param 	path	地址
	 * 	@return	Enumeration
	 */
	public static List<URL> getResources(String path) {
		try {
			return CollUtils.newArrayList(ClassUtils.getClassLoader().getResources(path));
		} catch (Exception e) {
			throw new RuntimeIoException(e.getMessage(), e);
		}
	}
	
	/**	
	 * 	获取上下文路径地址
	 * 
	 * 	@author Pan
	 * 	@return	String
	 */
	public static String getPath() {
		URL resource = ClassUtils.getClassLoader().getResource("");
		if (resource == null) {
			return null;
		}
		String path = resource.getPath();
		if (null != path && path.startsWith("/")) {
			return path.substring(1);
        }
        return path;
	}
	
	/**	
	 * 	获取指定类所在的目录下的路径地址
	 * 
	 * 	@author Pan
	 * 	@param  clazz	类
	 * 	@return	String
	 */
	public static String getPath(Class<?> clazz) {
		URL url = clazz.getResource("");
		Assert.isNotNull(url);

		String protocol = url.getProtocol();
		String filePath = url.getFile();
		
		if (protocol.startsWith(ResourceType.PROTOCOL_JAR.getValue())) {
			JarFile jarFile = getJarFile(url);
			if (jarFile == null) {
				return null;
			}
			return StringUtils.append(jarFile.getName(), "\\", ClassUtils.getPackageName(clazz).replace(".", "\\"));
		}
		
		if (protocol.startsWith(FileMatchType.FILE.getValue())) {
			if (filePath.charAt(0) == '/') {
				return filePath.substring(1);
			}
			return StringUtils.replace(filePath, "file:/", "");
		}
		return null;
	}
	
	/**	
	 * 	根据类获取指定的JarFile对象
	 * 
	 * 	@author Pan
	 * 	@param 	clazz	URL对象
	 * 	@return	JarFile
	 */
	public static JarFile getJarFile(Class<?> clazz) {
		try {
			URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
			URLConnection openConnection = url.openConnection();
			if (openConnection == null || !isJarFileUrl(url)) {
				return null;
			}
			return ((JarURLConnection) openConnection).getJarFile();
		} catch (Exception e) {
			throw new NotFoundException(e.getMessage(), e);
		}
	}
	
	/**	
	 * 	获取JarFile
	 * 	
	 * 	@author Pan
	 * 	@param 	url		URL对象
	 * 	@return	JarFile
	 */
	public static JarFile getJarFile(URL url) {
		Assert.isNotNull(url);
		
		try {
			URLConnection openConnection = url.openConnection();
			if (openConnection == null || !isJarFileUrl(url)) {
				return null;
			}
			return ((JarURLConnection) openConnection).getJarFile();
		} catch (Exception e) {
			throw new NotFoundException(e.getMessage(), e);
		}
	}
	
	/**
	 * 	验证是否为File资源
	 * 	
	 * 	@author Pan
	 * 	@param 	url		URL对象
	 * 	@return	boolean
	 */
	public static boolean isFileUrl(URL url) {
		return Eq.strOr(url.getProtocol(), ResourceType.PROTOCOL_FILE, ResourceType.PROTOCOL_VFS, ResourceType.PROTOCOL_VFSFILE);
	}
	
	/**
	 * 	验证是否为jar资源
	 * 	<br>协议为"jar"、"war"、"zip"、"vsfzip"、"wsjar"
	 *	<br>如果只是判断 URL是否指向一个 jar资源使用此方法
	 *	
	 * 	@author Pan
	 * 	@param 	url		URL对象
	 * 	@return	boolean
	 */
	public static boolean isJarURL(URL url) {
		return Eq.strOr(url.getProtocol(), ResourceType.PROTOCOL_JAR, 
				ResourceType.PROTOCOL_WAR, ResourceType.PROTOCOL_ZIP,
				ResourceType.PROTOCOL_VFSZIP, ResourceType.PROTOCOL_WSJAR);
	}

	/**
	 * 	验证是否为jarFile资源
	 * 	<br>协议为"file"及路径以".jar"结尾返回true
	 * 	<br>验证是否为本地jar文件
	 * 	
	 * 	@author Pan
	 * 	@param 	url		URL对象
	 * 	@return	boolean
	 */
	public static boolean isJarFileUrl(URL url) {
		return Eq.str(ResourceType.PROTOCOL_FILE.getValue(), url.getProtocol()) && url.getPath().toLowerCase().endsWith(FileMatchType.JAR.getValue());
	}
}
